package com.capsulode.jdbc;


import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * 用来构建SQL.
 */
public class DefaultSqlBuilder implements SqlBuilder {
    private StringBuilder sql = new StringBuilder();
    private List<Object> params = new ArrayList<>();

    private boolean _hasWhere = false;

    public DefaultSqlBuilder() {
    }

    public DefaultSqlBuilder(CharSequence sql, Object... params) {
        this.sql.append(sql);
        this.params.addAll(Arrays.asList(params));
        if (this.sql.indexOf(" where ") != -1) {
            _hasWhere = true;
        }
    }

    private boolean isNotPresentWhere() {
        //when __has no where__ it slows down performance, but this scenario happens only once.
        //After calling `where()` it works efficiently.
        return !_hasWhere && sql.indexOf(" where ") == -1;
    }

    protected DefaultSqlBuilder concat(CharSequence chars, Object... params) {
        sql.append(chars);
        if (params.length > 0) {
            this.params.addAll(Arrays.asList(params));
        }
        return this;
    }


    /**
     * Append the specified sql piece. Whitespace will be insert if necessary.
     */
    public DefaultSqlBuilder append(String piece, Object... params) {
        if (!Character.isWhitespace(piece.charAt(0)) &&
                (sql.length() > 0 && !Character.isWhitespace(sql.charAt(sql.length() - 1)))) {
            sql.append(' ');
        }
        concat(piece, params);
        return this;
    }

    /**
     * Add the specified sql piece to the beginning.
     */
    public DefaultSqlBuilder prepend(String piece, Object... params) {
        sql.insert(0, piece);
        if (params.length > 0) {
            Collection<Object> originParams = this.params;
            this.params = Arrays.asList(params);
            this.params.addAll(originParams);
        }

        if (!Character.isWhitespace(piece.charAt(piece.length() - 1))) {
            sql.insert(piece.length(), ' ');
        }
        return this;
    }

    /**
     * Reset this builder and set content to _sql_.
     */
    public DefaultSqlBuilder sql(CharSequence sql) {
        this.sql = new StringBuilder(sql);
        this.params = new ArrayList<>();
        _hasWhere = false;
        return this;
    }


    /**
     * ` where 1=1` will be append if not contains ' where ' yet.
     */
    public DefaultSqlBuilder where() {
        if (isNotPresentWhere()) {
            sql.append(" where 1=1");
            _hasWhere = true;
        }
        return this;
    }

    /**
     * ` and condition` (or ` where  condition`) will be append if already contains ' where ' (or not).
     */
    public DefaultSqlBuilder where(CharSequence condition, Object... params) {
        sql.append(isNotPresentWhere() ? " where " : " and ").append(condition);
        this.params.addAll(Arrays.asList(params));
        _hasWhere = true;
        return this;
    }

    /**
     * ` and condition` (or ` where  condition`) will be append if already contains ' where ' (or not).
     * <p>This is an alias to {@link #where(CharSequence, Object...)}.</p>
     */
    public DefaultSqlBuilder and(CharSequence condition, Object... params) {
        return where(condition, params);
    }


    /**
     * ` and column = 0` will be append if `condition` == true.
     */
    public DefaultSqlBuilder equalsZeroIfTrue(CharSequence column, Boolean condition) {
        if (Boolean.TRUE.equals(condition)) {
            where().concat(" and ").concat(column).concat(" = ?", 0);
        }
        return this;
    }

    /**
     * ` and column = ?` will be append if `value` != null.
     */
    public DefaultSqlBuilder equalsIfPresent(CharSequence column, Object value) {
        if (value != null) {
            where().concat(" and ").concat(column).concat(" = ?", value);
        }
        return this;
    }

    /**
     * ` and column = ?` will be append if `value` isn't empty.
     */
    public DefaultSqlBuilder equalsIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" = ?", value);
        }
        return this;
    }

    /**
     * ` and column like ?` will be append if `value` isn't empty. Param is wrapped by two '%'.
     */
    public DefaultSqlBuilder likeIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" like ?", "%" + value + "%");
        }
        return this;
    }

    /**
     * ` and column like ?` will be append if `value` isn't empty. Param is suffixed by a '%'.
     */
    public DefaultSqlBuilder beginWithIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" like ?", value + "%");
        }
        return this;
    }

    /**
     * ` and column > 0` will be append if `condition` == true .
     */
    public DefaultSqlBuilder gtZeroIfTrue(CharSequence column, Boolean condition) {
        if (Boolean.TRUE.equals(condition)) {
            where().concat(" and ").concat(column).concat(" > ?", true);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` isn't empty.
     */
    public DefaultSqlBuilder gtIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" > ?", value);
        }
        return this;
    }

    /**
     * ` and column >= ?` will be append if `value` isn't empty.
     */
    public DefaultSqlBuilder geIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" >= ?", value);
        }
        return this;
    }

    /**
     * ` and column < ?` will be append if `value` isn't empty.
     */
    public DefaultSqlBuilder ltIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" < ?", value);
        }
        return this;
    }

    /**
     * ` and column <= ?` will be append if `value` isn't empty.
     */
    public DefaultSqlBuilder leIfNotEmpty(CharSequence column, String value) {
        if (value != null && value.length() > 0) {
            where().concat(" and ").concat(column).concat(" <= ?", value);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` != null.
     */
    public DefaultSqlBuilder gtIfPresent(CharSequence column, Object value) {
        if (value != null) {
            where().concat(" and ").concat(column).concat(" > ?", value);
        }
        return this;
    }

    /**
     * ` and column >= ?` will be append if `value` != null.
     */
    public DefaultSqlBuilder geIfPresent(CharSequence column, Object value) {
        if (value != null) {
            where().concat(" and ").concat(column).concat(" >= ?", value);
        }
        return this;
    }

    /**
     * ` and column < ?` will be append if `value` != null.
     */
    public DefaultSqlBuilder ltIfPresent(CharSequence column, Object value) {
        if (value != null) {
            where().concat(" and ").concat(column).concat(" < ?", value);
        }
        return this;
    }

    /**
     * ` and column <= ?` will be append if `value` != null.
     */
    public DefaultSqlBuilder leIfPresent(CharSequence column, Object value) {
        if (value != null) {
            where().concat(" and ").concat(column).concat(" <= ?", value);
        }
        return this;
    }

    /**
     * ` and column = ?` will be append if `value` > 0 .
     */
    public DefaultSqlBuilder equalsIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" = ?", value);
        }
        return this;
    }

    /**
     * ` and column != ?` will be append if `value` > 0 .
     */
    public DefaultSqlBuilder notEqualsIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" != ?", value);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` > 0.
     */
    public DefaultSqlBuilder gtIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" > ?", value);
        }
        return this;
    }

    /**
     * ` and column >= ?` will be append if `value` > 0.
     */
    public DefaultSqlBuilder geIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" >= ?", value);
        }
        return this;
    }

    /**
     * ` and column < ?` will be append if `value` > 0.
     */
    public DefaultSqlBuilder ltIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" < ?", value);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` > 0.
     */
    public DefaultSqlBuilder leIfPositive(CharSequence column, Number value) {
        if (value != null && value.doubleValue() > 0) {
            where().concat(" and ").concat(column).concat(" <= ?", value);
        }
        return this;
    }


    /**
     * ` and column = ?` will be append if `value` >= 0 .
     */
    public DefaultSqlBuilder equalsIfPositiveOrZero(CharSequence column, Number value) {
        if (value != null && value.doubleValue() >= 0) {
            where().concat(" and ").concat(column).concat(" = ?", value);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` >= 0.
     */
    public DefaultSqlBuilder gtIfPositiveOrZero(CharSequence column, Number value) {
        if (value != null && value.doubleValue() >= 0) {
            where().concat(" and ").concat(column).concat(" > ?", value);
        }
        return this;
    }

    /**
     * ` and column >= ?` will be append if `value` >= 0.
     */
    public DefaultSqlBuilder geIfPositiveOrZero(CharSequence column, Number value) {
        if (value != null && value.doubleValue() >= 0) {
            where().concat(" and ").concat(column).concat(" >= ?", value);
        }
        return this;
    }

    /**
     * ` and column < ?` will be append if `value` >= 0.
     */
    public DefaultSqlBuilder ltIfPositiveOrZero(CharSequence column, Number value) {
        if (value != null && value.doubleValue() >= 0) {
            where().concat(" and ").concat(column).concat(" < ?", value);
        }
        return this;
    }

    /**
     * ` and column > ?` will be append if `value` >= 0.
     */
    public DefaultSqlBuilder leIfPositiveOrZero(CharSequence column, Number value) {
        if (value != null && value.doubleValue() >= 0) {
            where().concat(" and ").concat(column).concat(" <= ?", value);
        }
        return this;
    }

    /**
     * ` and column between ? and ?` will be append if `from` and `to` are both not null.
     */
    public DefaultSqlBuilder betweenIfPresent(CharSequence column, Object from, Object to) {
        if (from != null && to != null) {
            where().concat(" and ").concat(column).concat(" between ? and ?", from, to);
        }
        return this;
    }


    /**
     * ` and column = 1` will be append if `test` is true.
     */
    public DefaultSqlBuilder equalTrueWhen(CharSequence column, Boolean test) {
        if (Boolean.TRUE.equals(test)) {
            where().concat(" and ").concat(column).concat(" = 1");
        }
        return this;
    }

    private static final DateTimeFormatter dayPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * ` and column > ?` will be append if `from` isn't empty.  Parameter will truncate all time properties.
     *
     * @throws IllegalArgumentException If `from` can not eval to java.util.Date.
     */
    public DefaultSqlBuilder dayFromIfNotEmpty(CharSequence column, String from) throws IllegalArgumentException {
        if (from != null && from.length() > 0) {
            LocalDate dayFrom = LocalDate.parse(from, dayPattern);
            where().concat(" and ").concat(column).concat(" > ?", dayFrom);
        }
        return this;
    }

    /**
     * ` and column < ?` will be append if `from` isn't empty.
     * Parameter will <strong>plus one day</strong> and truncate all time properties.
     *
     * @throws IllegalArgumentException If `from` can not eval to java.util.Date.
     */
    public DefaultSqlBuilder dayToIfNotEmpty(CharSequence column, String to) throws IllegalArgumentException {
        if (to != null && to.length() > 0) {
            LocalDate dayTo = LocalDate.parse(to, dayPattern)
                    .plusDays(1);
            where().concat(" and ").concat(column).concat(" < ?", dayTo);
        }
        return this;
    }

    /**
     * ` and column in (?,?,...)` will be append if `list` isn't empty.
     */
    public DefaultSqlBuilder inIfNotEmpty(CharSequence column, Collection list) {
        if (list != null && !list.isEmpty()) {
            where().concat(" and ").concat(column).concat(" in (");
            for (Object obj : list) {
                concat("?,", obj);
            }
            sql.setCharAt(sql.length() - 1, ')');
        }
        return this;
    }

    public String toString() {
        return sql.toString();
    }

    @Override
    public String getSql() {
        return sql.toString();
    }

    @Override
    public Object[] getParams() {
        return params.toArray();
    }
}
